/*
 *	DjoltNode.m
 *
 *	David Janes
 *	Discover Anywhere Mobile
 *	March 2010
 *
 *	Copyright 2010 Discover Anywhere Mobile
 */

#import "DjoltTemplate.h"
#import "DjoltNode.h"
#import "DjoltContext.h"
#import "DjoltFilter.h"
#import "DjoltToken.h"

@implementation DjoltNode
@synthesize parent;
@synthesize children;
@synthesize text;
@synthesize path;
@synthesize arguments;

+ (BOOL) getBool:(NSString*)_path fromDictionary:(NSDictionary*)_d otherwise:(BOOL)_otherwise
{
	id _value = [_d objectForKey:_path];
	if (_value == nil) {
		return	_otherwise;
	}

	if ([_value isKindOfClass:[NSNumber class]]) {
		return	[_value boolValue];
	} else if ([_value isKindOfClass:[NSString class]]) {
		return	[_value length];
	} else if ([_value isKindOfClass:[NSDictionary class]]) {
		return	[_value count];
	} else if ([_value isKindOfClass:[NSArray class]]) {
		return	[_value count];
	} else {
		return	_otherwise;
	}
}

+ (NSString*) getString:(NSString*)_path fromDictionary:(NSDictionary*)_d otherwise:(NSString*)_otherwise
{
	id _value = [_d objectForKey:_path];
	if (_value == nil) {
		return	_otherwise;
	}

	if ([_value isKindOfClass:[NSString class]]) {
		return	_value;
	} else if ([_value isKindOfClass:[NSNumber class]]) {
		return	[NSString stringWithFormat:@"%@", _value];
	} else {
		return	_otherwise;
	}
}

- (id) init
{
	return	[self initWithParent:nil];
}

- (id) initWithParent:(DjoltNode*)_parent
{
    if (self = [super init]) {
		parent = _parent;
		children = [[NSMutableArray alloc] initWithCapacity: 0];
	}

	return	self;
}

- (void) addChild:(DjoltNode*)_node
{
	[self.children addObject:_node];
}

- (void) assertIsClass:(id)_class
{
}

- (void) render:(DjoltContext*)_context into:(NSMutableString*)_result
{
	for (DjoltNode* child in self.children) {
		[child render:_context into:_result];
	}
}
@end

@implementation DjoltNodeRoot
@end

@implementation DjoltNodeText

- (id) initWithText:(NSString*)_text
{
    if (self = [super init]) {
		self.text = _text;
	}

	return	self;
}

- (void) render:(DjoltContext*)_context into:(NSMutableString*)_result
{
	[_result appendString:self.text];
}
@end

@implementation DjoltNodeVariable

- (id) initWithExpression:(NSString*)_expression
{
    if (self = [super initWithParent:nil]) {
		self.arguments = _expression;
	}

	return	self;
}

- (void) render:(DjoltContext*)_context into:(NSMutableString*)_result
{
	NSArray* tokens = [DjoltToken tokenize:self.arguments];
	if ([tokens count] != 1) {
		[NSException raise:@"Syntax Error" format:@"Expecting exactly 1 arguments, not '%@'", self.arguments];
	}

	DjoltToken* token = [tokens objectAtIndex:0];
	NSString* v = [token render:_context escape:YES];
	// NSLog(@"HERE:A v=%@", v);
	if (v) {
		[_result appendString:v];
	}
}
@end

@implementation DjoltNodeFor
- (id) initWithParent:(DjoltNode*)_parent arguments:(NSString*)_arguments
{
    if (self = [super initWithParent:_parent]) {
		NSArray* parts = [_arguments componentsSeparatedByString:@" "];

		int pi = 0;
		for (NSString* part in parts) {
			if ([part length] == 0) {
				continue;
			}

			switch (pi++) {
			case 0:
				self.path = part;
				break;

			case 1:
				if ([part compare:@"in"] != 0) {
					[NSException raise:@"Syntax Error" format:@"Expecting 'in', not '%@'", part];
				}
				break;

			case 2:
				self.arguments = part;
				break;

			default:
				[NSException raise:@"Syntax Error" format:@"Not expecting '%@'", part];
			}
		}

		if (pi != 3) {
			[NSException raise:@"Syntax Error" format:@"Insufficient arguments to 'for': %@", _arguments];
		}
	}

	return	self;
}

- (void) render:(DjoltContext*)_context into:(NSMutableString*)_result
{
	NSArray* values = [_context getArray:self.arguments otherwise:nil];
	if (values == nil) {
		return;
	}

	int lvalues = [values count];
	int count = -1;
	for (id value in values) {
		count += 1;

		NSDictionary* forloop = [[NSDictionary alloc] initWithObjectsAndKeys:
			[NSNumber numberWithInt:count + 1], @"counter",
			[NSNumber numberWithInt:count], @"counter0",
			[NSNumber numberWithInt:lvalues - count], @"revcounter",
			[NSNumber numberWithInt:lvalues - count - 1], @"revcounter0",
			[NSNumber numberWithBool:count == 0], @"first",
			[NSNumber numberWithBool:count == (lvalues - 1)], @"last",
			nil];

		[_context push];
		[_context setObject:value forKey:self.path];
		[_context setObject:forloop forKey:@"forloop"];
		
		[super render:_context into:_result];

		[_context pop];

		[forloop release];
	}
}
@end

@implementation DjoltNodeInvertable
- (DjoltNodeInvertable*) invert
{
	NSLog(@"XXX RAISE EXCEPTION HERE");
	return	nil;
}
@end

@implementation DjoltNodeIf
- (id) initWithParent:(DjoltNode*)_parent arguments:(NSString*)_arguments
{
    if (self = [super initWithParent:_parent]) {
		self.arguments = _arguments;
		self->invert = NO;
	}

	return	self;
}

- (id) initWithIf:(DjoltNodeInvertable*)_if_node
{
    if (self = [super initWithParent:_if_node.parent]) {
		self.arguments = _if_node.arguments;
		self->invert = !_if_node->invert;
	}

	return	self;
}

- (void) render:(DjoltContext*)_context into:(NSMutableString*)_result
{
	/*
	 *	XXX: Improvement Point
	 *	- this is not at all rigourous, plus it accepts
	 *	  mixed 'and' and 'or's in a very non Django-way
	 */
	BOOL is_true = YES;
	BOOL expecting_variable = YES;
	BOOL is_not = NO;

	BOOL op_and = NO;
	BOOL op_or = NO;

	NSArray* parts = [self.arguments componentsSeparatedByString:@" "];
	for (NSString* part in parts) {
		if ([part length] == 0) {
			continue;
		}

		if ([part compare:@"or"] == 0) {
			if (expecting_variable) {
				[NSException raise:@"Expecting a variable" format:@"Expecting a variable, not '%@'", part];
			}

			expecting_variable = YES;
			op_or = YES;
		} else if ([part compare:@"and"] == 0) {
			if (expecting_variable) {
				[NSException raise:@"Expecting a variable" format:@"Expecting a variable, not '%@'", part];
			}

			expecting_variable = YES;
			op_and = YES;
		} else if ([part compare:@"not"] == 0) {
			if (!expecting_variable) {
				[NSException raise:@"Syntax Error" format:@"Not expecting a variable, not '%@'", part];
			}

			is_not = !is_not;
		} else {
			if (!expecting_variable) {
				[NSException raise:@"Syntax Error" format:@"Not expecting a variable, not '%@'", part];
			}

			BOOL v = [_context getBool:part otherwise:NO];

			if (is_not) {
				v = !v;
				is_not = NO;
			}

			if (op_and) {
				is_true = v && is_true;
			} else if (op_or) {
				is_true = v || is_true;
			} else {
				is_true = v;
			}

			expecting_variable = NO;
		}
	}

	if (op_and && op_or) {
		[NSException raise:@"Cannot mix 'and' and 'or'" format:@"Cannot mix 'and' and 'or'"];
	}

	if (self->invert) {
		is_true = !is_true;
	}

	if (is_true) {
		[super render:_context into:_result];
	}
}

- (DjoltNodeInvertable*) invert
{
	return	[[DjoltNodeIf alloc] initWithIf:self];
}
@end

@implementation DjoltNodeIfEqual
- (id) initWithParent:(DjoltNode*)_parent arguments:(NSString*)_arguments inverted:(BOOL)_inverted
{
    if (self = [super initWithParent:_parent]) {
		self.arguments = _arguments;
		self->invert = _inverted;
	}

	return	self;
}

- (id) initWithIfEqual:(DjoltNodeInvertable*)_ifequal_node
{
    if (self = [super initWithParent:_ifequal_node.parent]) {
		self.arguments = _ifequal_node.arguments;
		self->invert = !_ifequal_node->invert;
	}

	return	self;
}

- (void) render:(DjoltContext*)_context into:(NSMutableString*)_result
{
	NSArray* tokens = [DjoltToken tokenize:self.arguments];
	if ([tokens count] != 2) {
		[NSException raise:@"Syntax Error" format:@"Expecting exactly two arguments, not '%@'", self.arguments];
	}

	DjoltToken* a = [tokens objectAtIndex:0];
	NSString* av = [a render:_context escape:NO];

	DjoltToken* b = [tokens objectAtIndex:1];
	NSString* bv = [b render:_context escape:NO];

	// NSLog(@"av='%@' bv='%@'", av, bv);

	BOOL is_true = [av compare:bv] == 0;
	if (self->invert) {
		is_true = !is_true;
	}

	if (is_true) {
		[super render:_context into:_result];
	}
}

- (DjoltNodeInvertable*) invert
{
	return	[[DjoltNodeIfEqual alloc] initWithIfEqual:self];
}
@end

@implementation DjoltNodeComment
- (id) initWithParent:(DjoltNode*)_parent
{
    if (self = [super initWithParent:_parent]) {
	}

	return	self;
}

- (void) render:(DjoltContext*)_context into:(NSMutableString*)_result
{
}
@end

